<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="CJPUt" id="CJPUt"><span data-lake-id="ub47250f6" id="ub47250f6">典型回答</span></h1>
  <p data-lake-id="u0df5032a" id="u0df5032a"><br></p>
  <p data-lake-id="u8dfd6477" id="u8dfd6477"><span data-lake-id="ucf3fff99" id="ucf3fff99">TCC是Try-Confirm-Cancel的缩写，它是一种分布式事务解决方案，采用了基于业务逻辑的补偿机制，将整个分布式事务分解为若干个子事务，每个子事务都有一个try、confirm和cancel三个操作，通过这些操作来实现分布式事务的执行和回滚</span></p>
  <p data-lake-id="ud9c7596a" id="ud9c7596a"><img src="https://cdn.nlark.com/yuque/0/2023/jpeg/5378072/1676882484053-9341d4bb-0cf8-493d-bfe8-f56530807454.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_24%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="ueacab2c3" id="ueacab2c3"><span data-lake-id="ucc18af69" id="ucc18af69">​</span><br></p>
  <p data-lake-id="u5d1f8003" id="u5d1f8003"><span data-lake-id="udc80f816" id="udc80f816">具体来说，TCC事务包括以下三个步骤：</span></p>
  <ol list="ue96e2f08">
   <li fid="u6ae39a33" data-lake-id="u9109d5b2" id="u9109d5b2"><span data-lake-id="u19160e70" id="u19160e70">Try：在try阶段，参与者尝试执行本地事务，</span><strong><span data-lake-id="ueb92c7e2" id="ueb92c7e2">并对全局事务预留资源</span></strong><span data-lake-id="ud7dd3db1" id="ud7dd3db1">。如果try阶段执行成功，参与者会返回一个成功标识，否则会返回一个失败标识。</span></li>
  </ol>
  <p data-lake-id="u36614d52" id="u36614d52"><br></p>
  <ol list="ue96e2f08" start="2">
   <li fid="u6ae39a33" data-lake-id="ud4ff9413" id="ud4ff9413"><span data-lake-id="ub8cf138a" id="ub8cf138a">Confirm：如果所有参与者的try阶段都执行成功，则协调者通知所有参与者提交事务，那么就要执行confirm阶段，这时候参与者将在本地提交事务，并释放全局事务的资源。</span></li>
  </ol>
  <p data-lake-id="u6ca72fea" id="u6ca72fea"><br></p>
  <ol list="ue96e2f08" start="3">
   <li fid="u6ae39a33" data-lake-id="u80226779" id="u80226779"><span data-lake-id="u44b06ac5" id="u44b06ac5">Cancel：如果任何一个参与者在try阶段执行失败，则协调者通知所有参与者回滚事务。那么就要执行cancel阶段，还有就是，如果某个参与者在try阶段执行成功，但是在confirm阶段执行失败，则需要执行cancel操作，将之前预留的资源释放掉。</span></li>
  </ol>
  <p data-lake-id="u02f01069" id="u02f01069"><br></p>
  <p data-lake-id="u1d6a6336" id="u1d6a6336"><span data-lake-id="u38802b7f" id="u38802b7f">以下是一个简单的TCC事务的例子，假设有一个转账服务，需要从A账户中转移到B账户中100元、C账户中200元：</span></p>
  <p data-lake-id="uea718bf2" id="uea718bf2"><span data-lake-id="ude89b949" id="ude89b949">​</span><br></p>
  <ol list="u501c9866">
   <li fid="u723732e6" data-lake-id="ua3fdc649" id="ua3fdc649"><span data-lake-id="ud6cd73b8" id="ud6cd73b8">Try阶段：转账服务首先尝试将A账户的金额冻结300元。</span></li>
  </ol>
  <p data-lake-id="uf9adee5a" id="uf9adee5a"><br></p>
  <ol list="u501c9866" start="2">
   <li fid="u723732e6" data-lake-id="uf5bfbd97" id="uf5bfbd97"><span data-lake-id="u89f84a7e" id="u89f84a7e">Confirm阶段：如果所有的try操作都执行成功，转账服务将尝试执行解冻并转账，将金额转到B账户和C账户中。</span></li>
  </ol>
  <p data-lake-id="u72147ca1" id="u72147ca1"><br></p>
  <ol list="u501c9866" start="3">
   <li fid="u723732e6" data-lake-id="uabae2388" id="uabae2388"><span data-lake-id="uf40ede8b" id="uf40ede8b">Cancel阶段：如果try过程中，某个转账事务执行失败。那么将执行解冻，将300元解冻。如果在confirm过程中，A-&gt;C的转账成功，但是A-&gt;B的转账失败，则再操作一次C-&gt;A的转账，将钱退回去。</span></li>
  </ol>
  <p data-lake-id="uf8c226d9" id="uf8c226d9"><span data-lake-id="u34a10729" id="u34a10729">​</span><br></p>
  <p data-lake-id="u74de02c5" id="u74de02c5"><span data-lake-id="u19794d49" id="u19794d49">TCC这种事务方案有以下优缺点：</span></p>
  <p data-lake-id="uddf7dc63" id="uddf7dc63"><span data-lake-id="uf2cf604a" id="uf2cf604a">优点：</span></p>
  <ol list="uc1821d83">
   <li fid="u22b71799" data-lake-id="u48309616" id="u48309616"><span data-lake-id="u58d79eff" id="u58d79eff">灵活性：TCC适用于不同类型的业务场景，例如账户转账、库存扣减等，能够根据业务逻辑实现精细的事务控制。</span></li>
   <li fid="u22b71799" data-lake-id="u053839dc" id="u053839dc"><span data-lake-id="ub1e5444b" id="ub1e5444b">高可用性：TCC使用分布式锁来保证分布式事务的一致性，即使其中一个节点出现故障，也不会影响整个系统的运行。</span></li>
   <li fid="u22b71799" data-lake-id="u813a5f8f" id="u813a5f8f"><span data-lake-id="u2fb7e4a1" id="u2fb7e4a1">可扩展性：TCC采用分阶段提交的方式，支持横向扩展，可以适应更多的并发访问和业务场景。</span></li>
   <li fid="u22b71799" data-lake-id="u8b7b1527" id="u8b7b1527"><span data-lake-id="ueab85896" id="ueab85896">性能：TCC相对于2PC来说，具有更好的性能表现</span></li>
  </ol>
  <p data-lake-id="ub1807838" id="ub1807838"><br></p>
  <p data-lake-id="ucd5792ff" id="ucd5792ff"><span data-lake-id="u17b2f11b" id="u17b2f11b">缺点：</span></p>
  <ol list="ud44bd2a7">
   <li fid="ufa61e40c" data-lake-id="udf180322" id="udf180322"><span data-lake-id="u67ce9a50" id="u67ce9a50">实现复杂：TCC需要实现Try、Confirm和Cancel三个操作，每个操作都需要实现正确的业务逻辑和补偿机制，代码实现比较复杂。</span></li>
   <li fid="ufa61e40c" data-lake-id="ue532b235" id="ue532b235"><span data-lake-id="u59d68f21" id="u59d68f21">存在</span><strong><span data-lake-id="uad7a41c6" id="uad7a41c6">悬挂事务问题</span></strong><span data-lake-id="ue002b195" id="ue002b195">：TCC的实现方式存在悬挂事务的问题，即在执行过程中可能会有部分子事务成功，而其他子事务失败，导致整个事务无法回滚或提交。</span></li>
   <li fid="ufa61e40c" data-lake-id="ue6f32762" id="ue6f32762"><strong><span data-lake-id="u6501f0ca" id="u6501f0ca">空回滚问题</span></strong><span data-lake-id="ubd80e7e9" id="ubd80e7e9">：TCC中的Try过程中，有的参与者成功了，有的参与者失败了，这时候就需要所有参与者都执行Cancel，这时候，对于那些没有Try成功的参与者来说，本次回滚就是一次空回滚。需要在业务中做好对空回滚的识别和处理，否则就会出现异常报错的情况，甚至可能导致Cancel一直失败，最终导致整个分布式事务失败。</span></li>
   <li fid="ufa61e40c" data-lake-id="ud7b688a1" id="ud7b688a1"><span data-lake-id="u8ea48a55" id="u8ea48a55">业务代码侵入性：TCC需要将事务操作拆分为Try、Confirm和Cancel三个步骤，对业务代码有一定的侵入性，需要针对不同的业务场景进行实现。</span></li>
  </ol>
  <p data-lake-id="u45a863df" id="u45a863df"><br></p>
  <p data-lake-id="ua51872fd" id="ua51872fd"><br></p>
  <p data-lake-id="u56d617ae" id="u56d617ae"><u><span data-lake-id="ua993e9f0" id="ua993e9f0">但是其实，在实际的应用过程中，很多公司用的TCC并不是完全严格的按照上述的过程的。有的时候是这么用的：Try的时候预占用资源，如果成功了，就执行Confirm，失败了就执行Cancel，但是如果Confirm失败了或者Cancel失败了，则进行重试，直至成功。</span></u></p>
  <p data-lake-id="u21d1b796" id="u21d1b796"><u><span data-lake-id="u55b03790" id="u55b03790">​</span></u><br></p>
  <p data-lake-id="ue0f06c9f" id="ue0f06c9f"><u><span data-lake-id="u4ea3e609" id="u4ea3e609">之所以可以这么做，主要是因为在Try的过程中已经锁定了资源，那么在Confirm的时候，大概率是可以成功，而如果Confirm失败就执行Cancel，就会导致可能只是因为网络原因导致的时候就使得整个事务都Cancel了，而且这时候如果Cancel再失败怎么办呢？整个方案就会变得更加复杂了。</span></u></p>
  <p data-lake-id="u1f20a3a3" id="u1f20a3a3"><br></p>
  <h1 data-lake-id="m7sMm" id="m7sMm"><span data-lake-id="uccd3be5f" id="uccd3be5f">扩展知识</span></h1>
  <p data-lake-id="ub2d73860" id="ub2d73860"><br></p>
  <h2 data-lake-id="dNQXc" id="dNQXc"><span data-lake-id="u177658a3" id="u177658a3">TCC和2PC有什么区别？</span></h2>
  <p data-lake-id="u7eedfa0b" id="u7eedfa0b"><br></p>
  <p data-lake-id="uce2cf4e1" id="uce2cf4e1"><span data-lake-id="ub993e7bc" id="ub993e7bc">首先，</span><strong><span data-lake-id="ubfa5581c" id="ubfa5581c">二者的实现机制不同</span></strong><span data-lake-id="u46929a99" id="u46929a99">，2PC使用协调者和参与者的方式来实现分布式事务，而TCC采用分阶段提交的方式。</span></p>
  <p data-lake-id="u4b8d7813" id="u4b8d7813"><span data-lake-id="u7e73eb4e" id="u7e73eb4e">​</span><br></p>
  <p data-lake-id="u971946de" id="u971946de"><span data-lake-id="u47c87f10" id="u47c87f10">​</span><br></p>
  <p data-lake-id="uf27bc478" id="uf27bc478"><span data-lake-id="u2c124d6e" id="u2c124d6e">处理方式不同，2PC采用预写式日志的方式，在提交和回滚阶段需要协调者和参与者之间进行多次网络通信，整个事务处理过程较为复杂。TCC则不需要网络通信，只需要在Try、Confirm和Cancel阶段执行相应的业务逻辑。</span></p>
  <p data-lake-id="u98c509f9" id="u98c509f9"><span data-lake-id="uad7f931e" id="uad7f931e">​</span><br></p>
  <p data-lake-id="u412c2bd1" id="u412c2bd1"><span data-lake-id="u2679ded1" id="u2679ded1">异常处理不同，2PC需要处理网络、节点故障等异常情况，可能会导致整个事务无法提交或回滚，处理异常情况的复杂度较高。而TCC只需要处理业务异常情况，异常处理相对简单。</span></p>
  <p data-lake-id="u2516ee81" id="u2516ee81"><span data-lake-id="ua2b63d77" id="ua2b63d77">​</span><br></p>
  <p data-lake-id="u0578ed8e" id="u0578ed8e"><span data-lake-id="u45f3a20e" id="u45f3a20e">适用场景不同，2PC适用于对事务一致性要求较高的场景，例如银行转账等，需要保证数据一致性和完整性。而TCC适用于对事务一致性要求不那么高的场景，例如电商库存扣减等，需要保证数据最终一致性即可。</span></p>
  <p data-lake-id="u7b8cd0c0" id="u7b8cd0c0"><span data-lake-id="ub614dc78" id="ub614dc78">​</span><br></p>
  <blockquote data-lake-id="u77950e8a" id="u77950e8a">
   <p data-lake-id="u32419a25" id="u32419a25"><span data-lake-id="u39afa507" id="u39afa507">最初TCC的设计是强一致性，基本上一次事务执行完之后，数据是一致的，要么都commit，要么都cancel。</span></p>
   <p data-lake-id="u77e25d20" id="u77e25d20"><span data-lake-id="u865836cd" id="u865836cd">​</span><br></p>
   <p data-lake-id="u6a510434" id="u6a510434"><span data-lake-id="uc808c5f7" id="uc808c5f7">但是其实在实际使用过程中，可能会采用最终一致性的思想，比如commit失败之后，进行异步重试让他尝试成功，而不是立刻cancel</span></p>
  </blockquote>
  <p data-lake-id="udf5971fd" id="udf5971fd"><span data-lake-id="uec5b10ed" id="uec5b10ed" class="lake-fontsize-12" style="color: rgb(55, 65, 81); background-color: rgb(247, 247, 248)">​</span><br></p>
  <h2 data-lake-id="uRESs" id="uRESs"><span data-lake-id="ubcda33e5" id="ubcda33e5">Cancel失败了怎么办？</span></h2>
  <p data-lake-id="u5e9f1300" id="u5e9f1300"><br></p>
  <p data-lake-id="ub97b5136" id="ub97b5136"><span data-lake-id="uae4f427d" id="uae4f427d" class="lake-fontsize-12" style="color: rgb(52, 53, 65)">在TCC中，try和confirm失败了都要调cancel，那么cancel失败了怎么办呢？</span></p>
  <p data-lake-id="u158906ab" id="u158906ab"><span data-lake-id="u2de5bbfc" id="u2de5bbfc" class="lake-fontsize-12" style="color: rgb(52, 53, 65)">​</span><br></p>
  <p data-lake-id="u621d7143" id="u621d7143"><span data-lake-id="u0fed1c76" id="u0fed1c76">一般有以下几种处理手段：</span></p>
  <p data-lake-id="uec6d9ad6" id="uec6d9ad6"><span data-lake-id="u80603541" id="u80603541">​</span><br></p>
  <ol list="uad8fb9a5">
   <li fid="u812749ae" data-lake-id="u288f3637" id="u288f3637"><span data-lake-id="u390aece5" id="u390aece5">记录日志&amp;发送报警：将错误信息记录下来，方便后续分析和处理。并及时通知相关人员进行处理。</span></li>
   <li fid="u812749ae" data-lake-id="u0818472e" id="u0818472e"><span data-lake-id="u0c990116" id="u0c990116">自动重试：在一定程度上，可以通过自动重试的方式尝试多次执行Cancel操作，直到成功为止。</span></li>
   <li fid="u812749ae" data-lake-id="ub29557b7" id="ub29557b7"><span data-lake-id="ubc43226b" id="ubc43226b">人工干预：如果重试多次还是不成功， 可以报警，然后进行人工干预，可以尝试手动执行Cancel操作或者进行数据修复等。</span></li>
  </ol>
  <p data-lake-id="u2dd75fb3" id="u2dd75fb3"><span data-lake-id="u0bf08cdd" id="u0bf08cdd">​</span><br></p>
  <p data-lake-id="u99172212" id="u99172212"><span data-lake-id="u259de7ba" id="u259de7ba">​</span><br></p>
 </body>
</html>